
/** A print()-like messaging function. All arguments
    are PREpended to jQuery('#logArea'), along with a
    timestamp.
*/
function message()
{
    var av = Array.prototype.slice.apply(arguments,[0]);
    var ts = jQuery('<span>').addClass('message-timestamp').
        text( (new Date()).toLocaleString()+":" );
    av.unshift( ts );
    var l = jQuery('<div>');
    var k;
    for( k = 0; k < av.length; ++k )
    {
        l.append( av[k] );
        if( k < (av.length-1) ) l.append(' ');
    }
    jQuery('#logArea').prepend(l);
}


function createObjectInspector(from)
{
    var code = from ? from : jQuery('#textResponse').attr('value');
    if( ! code )
    {
        message("Enter a JSON message object before continuing!");
        return;
    }
    var req = null;
    try
    {
        req = new JSONMessage.Request(code);
    }
    catch(e)
    {
        alert("Creation of JSONMessage.Request failed while parsing JSON:\n"+e);
        return;
    }

    src = req.toJSONable();
    function insert( key, val )
    {
        var type = typeof val;
        var suffix = '';
        var ent = {};
        var k, v, cutoff = 32/*max visible length of values*/;
        if( JSONMessage.isArray( val ) )
        {
            ent.entries = [];
            for( k in val )
            {
                ent.entries.push( arguments.callee( k, val[k] ) );
            }
            suffix = '(length='+ent.entries.length+')';
        }
        else if( 'object' === typeof val )
        {
            ent.entries = [];
            var c = 0;
            for( k in val )
            {
                ent.entries.push( arguments.callee( k, val[k] ) );
                ++c;
            }
            suffix = '('+c+' props.)';
        }
        else if( 'function' === typeof val )
        {
        }
        else
        {
            v = JSON.stringify(val);
            if( v.length > cutoff )
            {
                v = v.substring(0,cutoff) +'...';
                ent.onclick = function(menuElement,jqElement)
                {
                    alert(key+"=\n"+val);
                };
            }
            suffix = '= '+v;
        }
        ent.title = type+' '+key+' '+suffix;
        return ent;
    }
    var menu = insert( 'root', src );
    var view = jQuery.createBogoMenu(menu);
    jQuery('#inspectorTest').empty().append(view);
}

function updateResponseView()
{
    var src = jQuery('#textResponse');
    var code = src.attr('value');
    try
    {
        createObjectInspector(JSON.parse(code));
    }
    catch(e)
    {
        alert("Exception: "+e);
    }
}

/**
    Sends a ChuckNorris event and displays the result
    in an alert() dialog.
*/
function chuckNorris()
{
    jQuery('#inspectorTest').empty();
    var m = new JSONMessage.Request();
    m.type('ChuckNorris');
    jQuery('#textRequest').attr('value',m.toJSON());
    m.post( {
        onResponse:function(resp) {
            if( 0 == resp.resultCode() )
            {
                var p = resp.payload();
                var msg = "Chuck Norris quote #"+p.number+" of "+p.of+":\n\n"
                    +p.quote
                jQuery('#textResponse').attr('value',resp.toJSON());
                updateResponseView();
                alert( msg );
            }
        },
        onError:function(request,opt)
        {
            alert("Chuck fucking failed:\n"+
                    JSON.stringify(opt,undefined,'  ')
                    );
        }
    });
}
function sendBrokenMessage()
{
    var m = new JSONMessage.Request();
    m.type('ChuckNorris');
    jQuery('#textRequest').attr('value',m.toJSON());
    m.post( {
        url:'intentionalError',
        /*
        onError:function(request,opt)
        {
            alert("Checking onError() handling:\n"+
                    JSON.stringify(opt,undefined,'  ')
                    );
        }
        */
        placeholder:undefined
    });
}

/**
    Uses the content of jQuery('#textRequest') to create
    a JSONMessage.Request object. It sends the message and
    prints the response to jQuery('#textResponse').
*/
function sendJSONMessage()
{
    jQuery('#inspectorTest').empty();
    var src = jQuery('#textRequest');
    var code = src.attr('value');
    if( ! code )
    {
        message("Enter a JSON message object before submitting.");
        return;
    }
    var req = null;
    try
    {
        // ensure that entered code is in JSON format...
        // The parens/braces are unfortunately necessary
        // to avoid an eval error.
        var jobj =
            //eval('['+code+'][0]')
            eval('('+code+')')
            ;
        req =
            new JSONMessage.Request(jobj) // works
            //new JSONMessage.Request(code) // also okay
            ;
        //alert(req ? req.toJSON() : "NULL JSONMessage.Request!");
    }
    catch(e)
    {
        alert("Creation of JSONMessage.Request failed while parsing JSON:\n"+e);
        return;
    }
    if( ! req )
    {
        alert("JSONMessage.Request object is null :(");
        return;
    }
    if( 0 && ('ping' === req.type()) )
    { /* just testing postOptions() */
        req.postOptions({url:'xxxYYY',method:'GET'});
    }
    var tgt = jQuery('#textResponse');
    tgt.attr('value','Waiting on response...');
    message("Posting ["+req.type()+"] request...");
    //alert( 'popt='+JSON.stringify( req.postOptions() ));
    req.post({
        onResponse:function(resp)
        {
            tgt.attr('value',resp.toJSON());
            //var iserr = resp.resultCode() ? true : false;
            message( "Result from server: resultCode="+resp.resultCode()+
                    ", resultText=["+resp.resultText()+"]."
            );
            updateResponseView();
        }
    });
}

/** Creates a JSONMessage.Request object from:

tag may either be a message type string (e.g. 'ping')
or a JSONMessage.Request instance.

payload may be null or an object containing
payload data for the message.

opt is an optional object to be assigned to request.opt(opt).

The request is toJSON()'d and jQuery('#textRequest') is
set to that value. jQuery('#textResponse') is cleared.

If the checkbox jQuery('#checkSendImmediately') is checked
then the message is also posted immediately.
*/
function createSimpleMessage(tag,payload,opt)
{
    var m = ( tag instanceof JSONMessage.Request )
        ? tag
        : (new JSONMessage.Request({type:tag}))
        ;

    if( payload ) m.payload(payload);
    if( opt ) m.set(opt);
    var src = jQuery('#textRequest');
    src.attr('value',m.toJSON());
    jQuery('#textResponse').attr('value','');
    src.fadeOut( 'fast', function(){src.fadeIn('fast');} );
    if( jQuery('#checkSendImmediately').is(':checked') )
    {
        sendJSONMessage();
    }
}

function setup_menu()
{
    /**
        Example menu structure:
    */
    function msgfunc(key,payload)
    {
        return function() { createSimpleMessage(key,payload); }
    }
    function msgitem(lbl,key,payload)
    {
        return { title:lbl, onclick:msgfunc(key,payload) };
    }

    //alert( JSON.stringify( tmerr.postOptions()  ));
    //alert(tmerr.toJSON());
   var messages = {
        opt:{
            doFlash:false
        },
        entries:[
            {title:'Server',
                entries:[
                    msgitem('Ping',
                        // These are all _basically_ equivalent:
                        'ping', {msg:'hi, world!'}
                        //(new JSONMessage.Request.Ping()).payload( {msg:'hi, world!'} )
                        //(new JSONMessage.Request({type:'ping',payload:{msg:'hi, world!'}}))
                    ),
/**
                    msgitem('$_ENV','env'),
                    msgitem('Server-side sleep 3sec', 'sleep',{seconds:3}),

                    {title:'Server sleep w/ client-side timeout',
                        onclick:function() {
                            var tmerr = new JSONMessage.Request({type:'sleep',payload:{seconds:2}});
                            jQuery('#textRequest').attr('value', tmerr.toJSON());
                            tmerr.postOptions({
                                timeout:1800,
                                onResponse:function(req,opt){
                                    alert("Sleep w/ timeout succeeded! This shouldn't have happened!");
                                },
                                onError:function(req,opt){
                                    var msg = "Sleep failed (as expected):\n"
                                        +opt.errorMessage+'\nRequest was:\n'
                                        +req.toJSON()
                                    ;
                                    jQuery('#textResponse').attr('value',msg);
                                }
                            });
                            message("Posting a sleep request where we expect a timeout: "+JSON.stringify(tmerr.postOptions()));
                            tmerr.post();
                        }
                    },
*/
                    {title:'$_SESSION',
                        entries:[
                            msgitem('Get...', new JSONMessage.Request.ServerSession(),{id:true,get:[]}),
                            msgitem('Set...',new JSONMessage.Request.ServerSession(),{id:true,set:{my:"value"}}),
                            msgitem('Clear session',new JSONMessage.Request.ServerSession(),{id:true,clearSession:true})
                        ]
                    }
                ]
            },
            {title:'Cookies',
                entries:[
                    msgitem('Set...','cookie',{set:{'my':'value'}}),
                    msgitem('Get one...','cookie',{get:['my']}),
                    msgitem('Get all...','cookie',{get:[]})
//NYI in the java back-end: msgitem('Clear all...','cookie',{clearCookies:true})
                ]
            },
            {title:'Misc.',
                entries:[
                    //msgitem('Throw a server-side exception', new JSONMessage.Request({type:'throw',payload:{errorCode:2,message:"Throwing from the server!"}}) ),
                    msgitem('Request ctor test',new JSONMessage.Request.Ping({options:{foo:"nothing"},payload:{foo:"also nothing"}})),
                    msgitem('What would Chuck Norris do?','ChuckNorris'),
                    msgitem('No such event','noSuchEvent'),
                    {title:'Toggle animations',
                        onclick:function(){
                            if( ('fx' in jQuery) )
                            {
                                jQuery.fx.off = !jQuery.fx.off;
                                message('jQuery.fx.off == '+jQuery.fx.off);
                                // Reminder: jQuery 1.2.x doesn't seem to support this.
                            }
                            else
                            {
                                message('Your jQuery appears to be missing jQuery.fx.off!');
                            }
                        }
                    }
                ]
            }
        ]
    };
    var holder = jQuery('#requestMenu');
    var bogoMenu = jQuery.createBogoMenu( messages );
    holder.empty().append( bogoMenu ).append('<br/>');
    // ^^^ BR there is a workaround for a scrolling misbehaviour when clicking the last item.
};

function changePostImpl(which)
{
    if( !( which in JSONMessage.Request.postImpl.concrete ) )
    {
        alert(which+' not found in JSONMessage.Request.postImpl.concrete!');
        return false;
    }
    JSONMessage.Request.prototype.postBackend = JSONMessage.Request.postImpl.concrete[which];
    var ta = jQuery('<textarea></textarea>').attr({rows:10,cols:80,value:''+JSONMessage.Request.prototype.postBackend});
    message("AJAX back-end changed to "+which+":<br/>",ta);
    return false/*for onclick handlers*/;
};

JSONMessage.Request.options.ajax.url = '/JSONMessage/dispatch';
jQuery(document).ready( function(JQ) {
    JQ.noConflict();
    if(1)
    { // get the header/about area out of the way when the page's title H1 is clicked.
        var head = jQuery('#headerArea');
        head.slideTime = 500;
        jQuery('h1').eq(0).toggle(
                function() { head.slideUp(head.slideTime); },
                function() { head.slideDown(head.slideTime); }
        );
    }
    if(1)
    {  // Add a visual notification when JSONMessage.Request messages are pending...
        var notif = jQuery('#ajaxNotification');
        notif.hide();
        notif.postCount = 0;
        notif.fadeSpeed = 'fast';
        notif.updateMessage = function(n)
        {
            this.postCount += n;
            this.text( this.postCount + ' pending JSONMessage.Request connection(s)...' );
            if( 1 == this.postCount ) this.fadeIn(this.fadeSpeed);
            else if( ! this.postCount ) this.fadeOut(this.fadeSpeed);
        };
        JSONMessage.Request.options.ajax.beforePost = function(req,opt)
        {
            notif.updateMessage(1);
        };
        JSONMessage.Request.options.ajax.afterPost = function(req,opt)
        {
            notif.updateMessage(-1);
            var tgt = jQuery('#textResponse');
            tgt.attr('value','');
        };
    }
    setup_menu();
    changePostImpl('XMLHttpRequest');
    if(1)
    {
        createSimpleMessage(new JSONMessage.Request.Ping({
            payload:{
                //id:'test',type:'foo', // demonstrate a corner-case bug in JSONMessage ctor logic
                foo:"hi, world"
            }
        }));
    }
    jQuery('textarea').attr({rows:12});
});
